package top.blackcat.learn.shiro.utils;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import top.blackcat.learn.shiro.enums.RedisKeyEnum;

import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


/****
 * jwt工具类
 */
@Slf4j
@Component
public class JWTUtils {


    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    @Value("${jwt.ttl}")
    private Integer jwtTtl;

    @Value("${jwt.salt}")
    private String salt;


    /***
     * 创建 token
     * @return
     */
    public String createToken(String username) {
        //  Header
        Map<String, Object> map = new HashMap<>(2);
        map.put("alg", "HS256");
        map.put("typ", "JWT");
        Date now = new Date();

        String token = JWT.create()
                /***********第一部分 Header（头部） ********/
                // Header（头部）
                .withHeader(map)
                /***********第二部分 Payload（负载）********/
                // iss: jwt签发者
                .withIssuer("message")
                // sub: jwt所面向的用户
                .withSubject(username)
                // aud: 接收jwt的一方
                .withAudience("client")
                // exp: jwt的过期时间，这个过期时间必须要大于签发时间
                .withExpiresAt(expireDate(now, 0, 0, 0, 0, jwtTtl, 0))
                // nbf: 定义在什么时间之前，该jwt都是不可用的.
                // .withNotBefore(new Date())
                // jti: jwt的唯一身份标识，主要用来作为一次性token,从而回避重放攻击。
                .withJWTId(IdUtil.simpleUUID())
                // iat: jwt的签发时间
                .withIssuedAt(now)
                /***********第三部分 Signature（签名）********/
                // Signature（签名）
                .sign(Algorithm.HMAC256(salt));
        Md5Hash tokenHash = new Md5Hash(token, username);
        String key = String.format(RedisKeyEnum.USER_TOKEN.getMsg(), tokenHash );
        stringRedisTemplate.opsForValue().set(key, token,Duration.of(jwtTtl, ChronoUnit.MINUTES));
        return key;
    }


    /***
     *
     * @param claims
     * @return
     */
    public String createTokenWithClaim(Map<String, String> claims) {
        Map<String, Object> map = new HashMap<>(2);
        map.put("alg", "HS256");
        map.put("typ", "JWT");
        Date now = new Date();
        JWTCreator.Builder builder = JWT.create();
        for (Map.Entry entry : claims.entrySet()) {
            builder.withClaim((String) entry.getKey(), (String) entry.getValue());
        }
        builder.withHeader(map)
                .withIssuer("message")
//                .withSubject("client")
                .withIssuedAt(now)
                .withExpiresAt(expireDate(now, 0, 0, 0, 0, jwtTtl, 0));
        String secret = IdUtil.simpleUUID();
        String token = builder.sign(Algorithm.HMAC256(secret));
        stringRedisTemplate.opsForValue().set(token + "-secret", secret);
        return token;
    }

    /**
     * 验证token的签名 防止伪造
     * @param token
     * @return
     */
    public DecodedJWT verify(String token) {
        DecodedJWT jwt = null;
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(salt))
                    .withIssuer("message")
                    .build();
            jwt = verifier.verify(token);
        } catch (TokenExpiredException e) {
            e.printStackTrace();
            log.error(e.getMessage());
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
        return jwt;
    }

    /***
     * @param token
     * @param username
     * @return
     */
    public String get(String token, String username) {
        DecodedJWT decodedJWT = JWT.decode(token);
        String re = "";
        Map claims = decodedJWT.getClaims();
        if (claims.containsKey(username)) {
            re = decodedJWT.getClaim(username).asString();
        }
        return re;
    }


    /***
     *
     * @param now
     * @param year
     * @param month
     * @param day
     * @param hour
     * @param minute
     * @param second
     * @return
     */
    public Date expireDate(Date now, int year, int month, int day, int hour, int minute, int second) {
        if (now == null) {
            now = new Date();
        }
        if (year != 0) {
            now = DateUtil.offset(now, DateField.YEAR, year);
        }
        if (month != 0) {
            now = DateUtil.offset(now, DateField.MONTH, month);
        }
        if (day != 0) {
            now = DateUtil.offset(now, DateField.DAY_OF_MONTH, day);
        }
        if (hour != 0) {
            now = DateUtil.offset(now, DateField.HOUR_OF_DAY, hour);
        }
        if (minute != 0) {
            now = DateUtil.offset(now, DateField.MINUTE, minute);
        }
        if (second != 0) {
            now = DateUtil.offset(now, DateField.SECOND, second);
        }
        return now;
    }
}
